import { html, unsafeHTML } from '@elenajs/core';
import { LitroPage } from '@beatzball/litro/adapter/elena/page';
import { definePageData } from '@beatzball/litro';
import { createError } from 'h3';
import type { Post } from 'litro:content';
import { getPosts } from 'litro:content';
import { siteConfig } from '../../server/starlight.config.js';
import { extractHeadings, addHeadingIds } from '../../src/extract-headings.js';
import { applyHighlighting } from '../../src/highlight.js';
import { starlightHead } from '../../src/route-meta.js';
// Re-exporting child components prevents Rollup tree-shaking.
export { StarlightPage } from '../../src/components/starlight-page.js';
export interface DocPageData {
doc: Post;
body: string;
toc: Array<{ depth: number; text: string; slug: string }>;
sidebar: typeof siteConfig.sidebar;
siteTitle: string;
currentSlug: string;
prevDoc: { label: string; href: string } | null;
nextDoc: { label: string; href: string } | null;
nav: typeof siteConfig.nav;
editUrl: string | null;
}
function computePrevNext(
sidebar: typeof siteConfig.sidebar,
currentSlug: string,
): { prevDoc: DocPageData['prevDoc']; nextDoc: DocPageData['nextDoc'] } {
const flat = sidebar.flatMap(g => g.items);
const idx = flat.findIndex(item => item.slug === currentSlug);
return {
prevDoc: idx > 0
? { label: flat[idx - 1].label, href: `/docs/${flat[idx - 1].slug}` }
: null,
nextDoc: idx < flat.length - 1
? { label: flat[idx + 1].label, href: `/docs/${flat[idx + 1].slug}` }
: null,
};
}
export const pageData = definePageData(async (event) => {
const slug = event.context.params?.slug ?? '';
const posts = await getPosts();
const doc = posts.find(p => p.url === `/content/docs/${slug}`);
if (!doc) {
throw createError({ statusCode: 404, message: `Doc not found: ${slug}` });
}
const toc = extractHeadings(doc.rawBody);
const body = applyHighlighting(addHeadingIds(doc.body));
const { prevDoc, nextDoc } = computePrevNext(siteConfig.sidebar, slug);
const editUrl = siteConfig.editUrlBase
? `${siteConfig.editUrlBase}/content/docs/${slug}.md`
: null;
return {
doc,
body,
toc,
sidebar: siteConfig.sidebar,
siteTitle: siteConfig.title,
currentSlug: slug,
prevDoc,
nextDoc,
nav: siteConfig.nav,
editUrl,
} satisfies DocPageData;
});
export async function generateRoutes(): Promise {
const posts = await getPosts();
return posts
.filter(p => p.url.startsWith('/content/docs/'))
.map(p => '/docs' + p.url.slice('/content/docs'.length));
}
export const routeMeta = {
head: starlightHead,
title: 'Docs — {{projectName}}',
};
export class DocPage extends LitroPage {
static override tagName = 'page-docs-slug';
render() {
const data = this.serverData as DocPageData | null;
if (!data?.doc) return html`Loading…
`;
const { doc, body, sidebar, siteTitle, currentSlug, toc, nav, prevDoc, nextDoc, editUrl } = data;
// Prev/next
const prevHtml = prevDoc
? `← ${prevDoc.label}`
: '';
const nextHtml = nextDoc
? `${nextDoc.label} →`
: '';
const prevNextHtml = (prevDoc || nextDoc)
? ``
: '';
const editHtml = editUrl
? `Edit this page
`
: '';
return html`
${unsafeHTML(body)}
${unsafeHTML(prevNextHtml)}
${unsafeHTML(editHtml)}
`;
}
}
DocPage.define();
export default DocPage;